﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Collections;
using System.Windows.Data;
using System.Windows.Media.Animation;
using System.Windows.Input;
using System.Windows.Media;

namespace GraphosControls
{
    public class ExpanderPanel : Canvas
    {
        public ExpanderPanel()
            : base()
        {
            Children.Capacity = 2;
            MainContentSizeChanged += ExpanderPanel_MainContentSizeChanged;
        }

        // content controls can raise this so the ExpanderPanel knows when to resize (eg, when a listbox changes its contents)
        public static readonly RoutedEvent MainContentSizeChangedEvent =
            EventManager.RegisterRoutedEvent("MainContentSizeChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Expander));
        public event RoutedEventHandler MainContentSizeChanged
        {
            add { AddHandler(MainContentSizeChangedEvent, value); }
            remove { RemoveHandler(MainContentSizeChangedEvent, value); }
        }

        /// <summary>
        /// Get the Button which is show when the ExpanderPanel is closed
        /// </summary>
        public Button MainButton
        {
            get { return Children[0] as Button; }
        }

        /// <summary>
        /// Gets the displayed content when the ExpanderPanel is open
        /// </summary>
        public FrameworkElement MainContent
        {
            get { return Children[1] as FrameworkElement; }
        }

        // set up the visual tree making sure the correct controls were added to the canvas
        protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
        {
            if (visualAdded != null)
            {
                switch (VisualChildrenCount)
                {
                    case 1:

                        // its got to be a button (for now)
                        Button button = visualAdded as Button;
                        if (button == null)
                            throw new Exception("Must be a button first in the collection");
                        Children[0] = button;

                        // set binding
                        Binding wBinding = new Binding("ActualWidth");
                        Binding hBinding = new Binding("ActualHeight");
                        wBinding.Source = MainButton;
                        hBinding.Source = MainButton;
                        SetBinding(WidthProperty, wBinding);
                        SetBinding(HeightProperty, hBinding);

                        // set some default values
                        MainButton.Click += Button_Click;
                        MainButton.Opacity = 1.0;
                        Panel.SetZIndex(MainButton, Panel.GetZIndex(this) + 1);
                        break;
                    case 2:
                        // set some default values
                        Children[1] = visualAdded as UIElement;
                        MainContent.Opacity = 0.0;
                        Panel.SetZIndex(MainContent, Panel.GetZIndex(this));
                        break;
                    default:
                        throw new Exception("Only two elements allowed in ExpanderPanel");
                }
            }
        }


        // open it back up if the mouse goes over the control while it is closing
        protected override void OnMouseEnter(System.Windows.Input.MouseEventArgs e)
        {
            base.OnMouseEnter(e);
            if (m_state == (State.Opened | State.Closing))
            {
                Open(0.1);
            }
            else if (m_state == State.Closing)
            {
                Open(0.15);
            }
        }

        // close the ExpanderPanel when the mouse leaves the area
        protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e)
        {
            base.OnMouseLeave(e);
            if (m_state == State.Opened)
            {
                Close(0.15, 1.5);
            }
        }

        // open the ExpanderPanel when a click occurs
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (m_state == State.Closed)
            {
                m_closedSize = new Size(ActualWidth, ActualHeight);
                Open(0.15);
            }
        }

        // open the panel when MainContentSizeChangedEvent is caught
        private void ExpanderPanel_MainContentSizeChanged(object sender, RoutedEventArgs e)
        {
            Open(0.15);
        }

        /// <summary>
        /// Open the ExpanderPanel
        /// </summary>
        /// <param name="time">The time span for each 100 units the control must resize</param>
        private void Open(double time)
        {
            UpdateLayout();
            MainContent.Measure(new Size(MaxWidth - (Margin.Right + Margin.Left), MaxHeight - (Margin.Bottom + Margin.Top)));

            m_state = (m_state & State.Closed) | State.Opening;

            Duration dur = TimeSpan.FromSeconds(time * (Math.Max(MainContent.DesiredSize.Width, MainContent.DesiredSize.Height) / 100.0));
            DoubleAnimation anim = new DoubleAnimation()
            {
                Duration = dur,
                AccelerationRatio = 0.75,
                DecelerationRatio = 0.25
            };
            
            // width animation
            DoubleAnimation a0 = anim.Clone();
            a0.From = MainButton.ActualWidth;
            a0.To = MainContent.DesiredSize.Width;
            a0.Freeze();
            MainButton.BeginAnimation(Button.WidthProperty, a0);

            // height animation
            DoubleAnimation a1 = anim.Clone();
            a1.From = MainButton.ActualHeight;
            a1.To = MainContent.DesiredSize.Height;
            a1.Freeze();
            MainButton.BeginAnimation(Button.HeightProperty, a1);

            // content opacity animation
            DoubleAnimation a2 = anim.Clone();
            a2.From = MainContent.Opacity;
            a2.To = 1.0;
            a2.Freeze();
            MainContent.BeginAnimation(FrameworkElement.OpacityProperty, a2);

            // button opacity animation
            DoubleAnimation a3 = anim.Clone();
            a3.From = MainButton.Opacity;
            a3.To = 0.0;
            a3.Completed += OpenAnimation_Completed;
            a3.Freeze();
            MainButton.BeginAnimation(Button.OpacityProperty, a3);

            Panel.SetZIndex(MainButton, Panel.GetZIndex(this));
            Panel.SetZIndex(MainContent, Panel.GetZIndex(this) + 1);
        }

        /// <summary>
        /// Close the ExpanderPanel
        /// </summary>
        /// <param name="time">The time the animation will take for each 100 units</param>
        /// <param name="delay">The main closing animation will delay starting this amount</param>
        private void Close(double time, double delay)
        {
            m_state = (m_state & State.Opened) | State.Closing;
            double width = m_closedSize.Width;
            double height = m_closedSize.Height;

            double total = time * (Math.Max(
                Math.Abs(ActualWidth - width), (Math.Abs(ActualHeight - height))) / 100.00);

            
            // content animation
            DoubleAnimation a0 = new DoubleAnimation()
            {
                From = MainContent.Opacity,
                To = 0.0,
                Duration = TimeSpan.FromSeconds(total + delay),
                DecelerationRatio = 0.75,
                AccelerationRatio = 0.25
            };
            a0.Freeze();
            MainContent.BeginAnimation(UIElement.OpacityProperty, a0);

            // base animation
            DoubleAnimation anim = new DoubleAnimation()
            {
                Duration = TimeSpan.FromSeconds(total),
                BeginTime = TimeSpan.FromSeconds(delay),
                DecelerationRatio = 0.99,
                AccelerationRatio = 0.01
            };

            // button opacity animation
            DoubleAnimation a1 = anim.Clone();
            a1.From = MainButton.Opacity;
            a1.To = 1.0;
            a1.Freeze();
            MainButton.BeginAnimation(Button.OpacityProperty, a1);

            // width animation
            DoubleAnimation a2 = anim.Clone();
            a2.From = MainButton.ActualWidth;
            a2.To = width;
            a2.Freeze();
            MainButton.BeginAnimation(Button.WidthProperty, a2);

            // height animation
            DoubleAnimation a3 = anim.Clone();
            a3.From = MainButton.ActualHeight;
            a3.To = height;
            a3.Completed += CloseAnimation_Completed;
            a3.Freeze();
            MainButton.BeginAnimation(Button.HeightProperty, a3);

            Panel.SetZIndex(MainButton, Panel.GetZIndex(this) + 1);
            Panel.SetZIndex(MainContent, Panel.GetZIndex(this));
        }

        // the open animation has finished
        private void OpenAnimation_Completed(object sender, EventArgs e)
        {

            (sender as AnimationClock).Completed -= OpenAnimation_Completed;
            if ((m_state & State.Opening) == State.Opening)
            {
                m_state = State.Opened;
                MainContent.Focus();
            }
            if (!IsMouseOver)
            {
                Close(0.15, 2.5);
            }

        }

        // the closing animation has finished
        private void CloseAnimation_Completed(object sender, EventArgs e)
        {
            (sender as AnimationClock).Completed -= CloseAnimation_Completed;
            if ((m_state & State.Closing) == State.Closing)
            {
                m_state = State.Closed;
                MainContent.MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
            }
        }

        private State m_state = State.Closed;
        private Size m_closedSize = new Size();

        [Flags]
        private enum State
        {
            Closed = 1,
            Opening = 2,
            Opened = 4,
            Closing = 8
        }

    }
}
